import info.monitorenter.gui.chart.Chart2D;
import info.monitorenter.gui.chart.IAxis;
import info.monitorenter.gui.chart.IRangePolicy;
import info.monitorenter.gui.chart.ITrace2D;
import info.monitorenter.gui.chart.rangepolicies.RangePolicyFixedViewport;
import info.monitorenter.gui.chart.traces.Trace2DSimple;
import info.monitorenter.gui.chart.traces.painters.TracePainterDisc;
import info.monitorenter.util.Range;

import java.awt.BasicStroke;
import java.awt.EventQueue;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.JLabel;
import java.awt.Font;
import javax.swing.JTextField;
import javax.swing.border.LineBorder;
import java.awt.Color;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.util.Iterator;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import javax.swing.JScrollPane;

public class MainWindow extends JFrame {
	private static final int ANIMATION_SPEED = 10000;

	private JPanel contentPane;
	private JTextField input_N;
	private JTextField input_fromX;
	private JTextField input_toX;
	private JTextField input_n;
	private JTextField input_k;
	private JButton btnSimulateK;
	private JButton btnSimulateN;
	private JButton btnGo;
	private Timer timer;
	private JButton stopButton;

	private ITrace2D trace;

	private static MainWindow frame;
	private DefaultTableModel tableModel;
	private JTable resultsTable;

	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					frame = new MainWindow();
					frame.setVisible(true);
				} catch (Exception e) {
					//e.printStackTrace();
				}
			}
		});
	}

	/**
	 * Create the frame.
	 */
	public MainWindow() {
		setResizable(false);
		setTitle("Hypergeometric Distribution Calculator");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBounds(100, 100, 1150, 750);
		contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		setContentPane(contentPane);
		contentPane.setLayout(null);

		// Input Panel
		{
			JPanel inputPanel = new JPanel();
			inputPanel.setBorder(new LineBorder(new Color(0, 0, 0)));
			inputPanel.setBounds(10, 11, 355, 210);
			contentPane.add(inputPanel);
			inputPanel.setLayout(null);

			// Input Labels
			{
				JLabel label = new JLabel("From:");
				label.setBounds(169, 132, 46, 14);
				inputPanel.add(label);

				JLabel label_1 = new JLabel("To:");
				label_1.setBounds(180, 157, 46, 14);
				inputPanel.add(label_1);

				JLabel label_2 = new JLabel("Successes  in Sample (x):");
				label_2.setBounds(10, 132, 170, 14);
				inputPanel.add(label_2);

				JLabel label_3 = new JLabel("Sample Size (n):");
				label_3.setBounds(10, 101, 216, 14);
				inputPanel.add(label_3);

				JLabel label_4 = new JLabel("Successes in Population (k):");
				label_4.setBounds(10, 70, 216, 14);
				inputPanel.add(label_4);

				JLabel label_5 = new JLabel("Population Size (N):");
				label_5.setBounds(10, 40, 216, 14);
				inputPanel.add(label_5);

				JLabel label_6 = new JLabel("Input Parameters:");
				label_6.setFont(new Font("Tahoma", Font.PLAIN, 14));
				label_6.setBounds(10, 11, 111, 26);
				inputPanel.add(label_6);
			}

			// Input Textboxes
			{
				input_fromX = new JTextField();
				input_fromX.setText("0");
				input_fromX.setColumns(10);
				input_fromX.setBounds(257, 129, 86, 20);
				inputPanel.add(input_fromX);

				input_toX = new JTextField("0");
				input_toX.setText("0");
				input_toX.setColumns(10);
				input_toX.setBounds(257, 157, 85, 20);
				inputPanel.add(input_toX);

				input_n = new JTextField();
				input_n.setText("0");
				input_n.setColumns(10);
				input_n.setBounds(257, 98, 86, 20);
				inputPanel.add(input_n);

				input_k = new JTextField();
				input_k.setText("0");
				input_k.setColumns(10);
				input_k.setBounds(257, 70, 86, 20);
				inputPanel.add(input_k);

				input_N = new JTextField();
				input_N.setText("0");
				input_N.setColumns(10);
				input_N.setBounds(257, 40, 86, 20);
				inputPanel.add(input_N);
			}
			
			btnGo = new JButton("Calculate");
			btnGo.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent arg0) {
					HypergeometricDistributionCalculator calc = new HypergeometricDistributionCalculator(
							Integer.parseInt(input_fromX.getText()), Integer
									.parseInt(input_toX.getText()), Integer
									.parseInt(input_N.getText()), Integer
									.parseInt(input_n.getText()), Integer
									.parseInt(input_k.getText()));
					calculateResults(calc);
				}
			});
			btnGo.setBounds(240, 182, 103, 23);
			inputPanel.add(btnGo);

			btnSimulateN = new JButton("Simulate n");
			btnSimulateN.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					HypergeometricDistributionCalculator calc = new HypergeometricDistributionCalculator(
							Integer.parseInt(input_fromX.getText()), Integer
									.parseInt(input_toX.getText()), Integer
									.parseInt(input_N.getText()), Integer
									.parseInt(input_n.getText()), Integer
									.parseInt(input_k.getText()));

					timer = new Timer();
					timer.schedule(new Simulate_n(calc), 0,
							ANIMATION_SPEED / Integer.parseInt(input_N.getText()));
				}
			});
			btnSimulateN.setBounds(8, 182, 103, 23);
			inputPanel.add(btnSimulateN);

			btnSimulateK = new JButton("Simulate k");
			btnSimulateK.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					HypergeometricDistributionCalculator calc = new HypergeometricDistributionCalculator(
							Integer.parseInt(input_fromX.getText()), Integer
									.parseInt(input_toX.getText()), Integer
									.parseInt(input_N.getText()), Integer
									.parseInt(input_n.getText()), Integer
									.parseInt(input_k.getText()));

					timer = new Timer();
					timer.schedule(new Simulate_k(calc), 0,
							ANIMATION_SPEED / Integer.parseInt(input_N.getText()));
				}
			});
			btnSimulateK.setBounds(121, 182, 103, 23);
			inputPanel.add(btnSimulateK);

			stopButton = new JButton("Stop");
			stopButton.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent arg0) {
					timer.cancel();
					enableInputs();
				}
			});
			stopButton.setBounds(8, 153, 103, 23);
			stopButton.setVisible(false);
			inputPanel.add(stopButton);
		}

		JScrollPane resultsScroll = new JScrollPane();
		resultsScroll.setBounds(10, 232, 355, 479);
		contentPane.add(resultsScroll);

		// Results Table
		Object[][] data = {};
		String[] columnNames = { "x", "N", "n", "k", "h(x; N, n, k)" };
		tableModel = new DefaultTableModel(data, columnNames);

		resultsTable = new JTable();
		resultsTable.setModel(tableModel);
		resultsTable.getColumnModel().getColumn(0).setPreferredWidth(60);
		resultsTable.getColumnModel().getColumn(1).setPreferredWidth(60);
		resultsTable.getColumnModel().getColumn(2).setPreferredWidth(60);
		resultsTable.getColumnModel().getColumn(3).setPreferredWidth(60);
		resultsTable.getColumnModel().getColumn(4).setPreferredWidth(112);
		resultsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
		resultsScroll.setViewportView(resultsTable);

		JPanel graphPanel = new JPanel();
		graphPanel.setBounds(375, 11, 759, 700);
		graphPanel.setLayout(null);
		contentPane.add(graphPanel);

		// Chart
		// Create a chart:
		Chart2D chart = new Chart2D();
		chart.setBounds(0, 0, 759, 700);
		// Create an ITrace:
		trace = new Trace2DSimple();
		// Add the trace to the chart. This has to be done before adding points
		// (deadlock prevention):
		chart.addTrace(trace);

		chart.getAxisX().setAxisTitle(
				new IAxis.AxisTitle("Number of Successes in Sample(x)"));
		chart.getAxisY().setAxisTitle(new IAxis.AxisTitle("h(x; N, n, k)"));

		IRangePolicy rangePolicy = new RangePolicyFixedViewport(new Range(0, 1));
		chart.getAxisY().setRangePolicy(rangePolicy);
		
		trace.addTracePainter(new TracePainterDisc(4));
		trace.setColor(Color.BLUE);
		trace.setStroke(new BasicStroke(2));
		graphPanel.add(chart);

	}

	// Update Results
	private void updateResults(Map<Integer, Double> probabilityList, int var_N,
			int var_n, int var_k) {
		// Update Table
		updateTableAndChart(probabilityList, var_N, var_n, var_k);
	}

	// Update Table
	private void updateTableAndChart(Map<Integer, Double> probabilityList,
			int var_N, int var_n, int var_k) {
		// Reinitialize table
		for (int i = 0; i < tableModel.getRowCount(); i = i + 0) {
			tableModel.removeRow(0);
		}
		// Reinitialize chart
		trace.removeAllPoints();

		double totalProbability = 0.0;
		for (Map.Entry<Integer, Double> probability : probabilityList
				.entrySet()) {
			
			double roundedProbability = (Math.round(probability.getValue() * 1000000000.0) / 1000000000.0);
			
			tableModel.addRow(new Object[] { probability.getKey(), var_N,
					var_n, var_k, roundedProbability });
			
			trace.addPoint(probability.getKey(), roundedProbability);
			totalProbability += probability.getValue();
			totalProbability = (Math.round(totalProbability * 1000000000.0) / 1000000000.0) ;
			if(totalProbability > 1)
				totalProbability = 1;
		}

		tableModel.addRow(new Object[] { "", "", "", "", "" });

		tableModel.addRow(new Object[] { "", "", "", "Total: ", totalProbability});
	}

	// Calculate Results
	private void calculateResults(HypergeometricDistributionCalculator calc) {
		updateResults(calc.getProbabilityListOfX(), calc.getVar_N(),
				calc.getVar_n(), calc.getVar_k());
	}

	// Simulate n
	private class Simulate_n extends TimerTask {
		Iterator nListIterator;
		HypergeometricDistributionCalculator calc;

		public Simulate_n(HypergeometricDistributionCalculator calc) {
			disableInputs();
			calculateResults(calc);

			nListIterator = calc
					.simulate_n(calc.getVar_fromX(), calc.getVar_toX(),
							calc.getVar_N(), calc.getVar_n(), calc.getVar_k())
					.entrySet().iterator();
			this.calc = calc;
		}

		public void run() {
			if (nListIterator.hasNext()) {
				Map.Entry entry = (Map.Entry) nListIterator.next();
				updateResults((Map<Integer, Double>) entry.getValue(),
						calc.getVar_N(), (Integer) entry.getKey(),
						calc.getVar_k());
			} else {
				enableInputs();
				this.cancel();
				timer.cancel();
			}
		}
	}

	// Simulate k
	private class Simulate_k extends TimerTask {
		Iterator kListIterator;
		HypergeometricDistributionCalculator calc;

		public Simulate_k(HypergeometricDistributionCalculator calc) {
			disableInputs();
			calculateResults(calc);

			kListIterator = calc
					.simulate_k(calc.getVar_fromX(), calc.getVar_toX(),
							calc.getVar_N(), calc.getVar_n(), calc.getVar_k())
					.entrySet().iterator();
			this.calc = calc;
		}

		public void run() {
			if (kListIterator.hasNext()) {
				Map.Entry entry = (Map.Entry) kListIterator.next();
				updateResults((Map<Integer, Double>) entry.getValue(),
						calc.getVar_N(), calc.getVar_n(),
						(Integer) entry.getKey());
			} else {
				enableInputs();
				this.cancel();
				timer.cancel();
			}
		}
	}

	private void disableInputs() {
		input_N.setEnabled(false);
		input_n.setEnabled(false);
		input_k.setEnabled(false);
		input_fromX.setEnabled(false);
		input_toX.setEnabled(false);
		btnSimulateK.setEnabled(false);
		btnSimulateN.setEnabled(false);
		btnGo.setEnabled(false);

		stopButton.setVisible(true);
		frame.repaint();
	}

	private void enableInputs() {
		input_N.setEnabled(true);
		input_n.setEnabled(true);
		input_k.setEnabled(true);
		input_fromX.setEnabled(true);
		input_toX.setEnabled(true);
		btnSimulateK.setEnabled(true);
		btnSimulateN.setEnabled(true);
		btnGo.setEnabled(true);

		stopButton.setVisible(false);
		frame.repaint();
	}
}
